package com.fhsk.file.manager.shiro.filter;

import com.alibaba.fastjson.JSON;
import com.fhsk.file.manager.shiro.token.JwtToken;
import com.fhsk.file.common.utils.RedisComponent;
import com.fhsk.file.common.vo.AjaxResult;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {

    private final String ACCESS_TOKEN = "Authorization";
    private final String TRUE = "true";

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Value("${shiro.unfilter.path}")
    private String unfilter;

    @Value("${anonymity.on-off}")
    private String on_off;

    private final static String SYSTEM_CONFIG_KEY = "system:config";//系统参数 redis key
    private final static String ANONYMITY_URI_PARAM_KEY = "ANONYMITY_URI";//匿名uri系统参数key
    private final static String PARAM_DESCRIPTION = "paramDescription";


    @Resource
    @Lazy
    private RedisComponent redisComponent;

    /**
     * 执行登录认证(判断请求头是否带上token)
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param mappedValue
     * @return
     */
    @SneakyThrows
    @Override
    protected boolean isAccessAllowed(ServletRequest httpServletRequest, ServletResponse httpServletResponse, Object mappedValue) {
        HttpServletRequest request = (HttpServletRequest) httpServletRequest;
        HttpServletResponse response = (HttpServletResponse) httpServletResponse;
        //如果存在,则进入executeLogin方法执行登入,检查token 是否正确
        try {
            if (isLoginAttempt(request, response)) {
                executeLogin(request, response);
                return true;
            } 
        } catch (Exception e) {
        	ServletOutputStream outputStream = response.getOutputStream();
            log.warn("token验证失败!");
            AjaxResult ajaxResult = new AjaxResult();
            ajaxResult.setCode(406);
            ajaxResult.setMsg("您的登录已失效，请退出重新登录！");
            String errorMsg = JSON.toJSONString(ajaxResult);
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setStatus(HttpStatus.OK.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            outputStream.write(errorMsg.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();
            return false;
        }
        return true;
    }

    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        if(req.getRequestURI().contains("ureport")) {
        	return false;
        }
        if (TRUE.equals(on_off)) {//如果开启匿名
            Map<String, Object> config = (Map<String, Object>) redisComponent.hget(SYSTEM_CONFIG_KEY, ANONYMITY_URI_PARAM_KEY);
            String uri = (null == config ? null : config.get(PARAM_DESCRIPTION).toString());
            if (!StringUtils.isEmpty(uri)) {
                String[] uris = uri.split(",");
                for (String path : uris) {
                    if (!StringUtils.isEmpty(path)) {
                        if (antPathMatcher.match(path, req.getRequestURI())) {
                            return false;
                        }
                    }
                }
            }
        } else {
            if (!StringUtils.isEmpty(unfilter)) {
                String[] paths = unfilter.split(",");
                for (String path : paths) {
                    boolean match = antPathMatcher.match(path, req.getRequestURI());
                    if (match) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 重写AuthenticatingFilter的executeLogin方法丶执行登陆操作
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        //先从请求头中获取token
        String token = httpServletRequest.getHeader(ACCESS_TOKEN);//Authorization
        //如果请求头中没有token,则从表单信息中获取
        if (StringUtils.isBlank(token)) {
            token = httpServletRequest.getParameter(ACCESS_TOKEN);
        }
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获, 反之则代表登入成功,返回true
        getSubject(request, response).login(jwtToken);
        return true;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest httpServletRequest, ServletResponse httpServletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) httpServletRequest;
        HttpServletResponse response = (HttpServletResponse) httpServletResponse;
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "*");
        response.setHeader("Access-Control-Max-Age", "86400");
        response.setHeader("Access-Control-Allow-Headers", "*,Content-Type,Authorization");
        // 如果是OPTIONS则结束请求
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            response.setStatus(HttpStatus.OK.value());
            ServletOutputStream outputStream = response.getOutputStream();
            outputStream.write("".getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();
            return false;
        }
        try {
            return super.preHandle(request, response);
        } catch (RuntimeException e) {
            return false;
        }
    }
}
